Skip to content

9.4 数据库实体

📝 模块更新日志 新特性*

+ 新增 `EFCore` 数据库操作 `EntityNotTenant` 和 `EntityBaseNotTenant` 实体基类 4\.9\.1\.11 ⏱️2023\.12\.04 [b62bfb1](https://gitee.com/dotnetchina/Furion/commit/b62bfb1a18d57f7fe3dd9548aa02372025dfed85)

特别提醒一旦定义了实体或改变了实体结构或实体配置,需要重新执行 Add-MigrationUpdate-Database 命令。

9.4.1 数据库实体

在面向对象开发思想中,最重要尤为对象二字,在 .NET 开发过去,操作数据库往往采用 DataTableDataSet 来接收数据库返回结果集,而操作数据库也离不开手写 sql 语句。

在过去面向过程和应用不发达的时代,这些操作确实好使。然后随着中国互联网网民的激增,电子化时代的到来,各行各业对应用需求也达到了前所未有的量级。

所以,在过去手写 sql 的时代各种问题显露无疑:

  • 程序员能力参差不齐,写出的 sql 性能自然也天差地别
  • sql 属于字符串硬编程,后期维护难上加难
  • 许多单表甚至多表结构一致,出现大量重复 sql 代码
  • sql 本身在不同的数据库提供器中写法有差,后续迁移头痛不已

当然,sql 是时代的产物,我们也离不开 sql,但对于大多数程序员和项目来说,sql 未必能够带给他们多大的效益。

所以,ORM 就诞生了,所谓的 ORM 就是对象关系映射,英文:Object Relational Mapping,简单点说,ORM 根据特有的 POCO 贫血模型 规则生成 sql 语句。大大避免了重复 sqlsql 能力参差不齐等问题。(当然 ORM 作者 sql 能力也会影响最终性能)

上面所说的 POCO 贫血模型正是我们本章节的 数据库实体

简单来说,数据库实体就是数据库表的类表现,通过一定的规则使这个类能够一一对应表结构。通常这样的类也称为:POCO 贫血模型,也就是只有定义,没有行为。

9.4.2 如何定义实体

Furion 框架提供多种定义实体的接口依赖:

  • IEntity:实体基接口,是所有实体的基接口
  • IEntityNotKey:无键实体接口,也就是视图、存储过程、函数依赖接口
  • EntityBase:实体基抽象类,内置了 IdTenantId 字段
  • Entity:实体通用抽象类,继承自 EntityBase,同时内置 CreatedTimeUpdatedTime 字段
  • EntityNotKey:无键实体抽象类,视图、存储过程、函数依赖抽象类
  • EntityNotTenant:实体通用抽象类,继承自 EntityBase,同时内置 CreatedTimeUpdatedTime 字段,没有 TenantId 字段,Furion 4.9.1.11+ 有效
  • EntityBaseNotTenant:实体基抽象类,内置了 Id,没有 TenantId 字段,Furion 4.9.1.11+ 有效

实体定义位置Furion 框架中有约定,实体统一定义在 Furion.Core 层。

9.4.2.1 实体继承选用原则

  • 如果你不需要 Furion 为实体添加任何内置特性,选用 IEntity,无键实体选用 IEntityNotKey
  • 如果你只需要 Id 属性,选用 EntityBase
  • 如果你需要 Furion 为你自动添加常用字段,则选用 Entity
  • 如果你需要视图、存储过程、函数可以通过 DbSet 操作,则继承 EntityNotKey

9.4.2.2 IEntity 示范:

using Furion.DatabaseAccessor;  

namespace Furion.Core  
{  
    public class User : IEntity  
    {  
        /// <summary>  
        /// 手工定义 Id  
        /// </summary>  
        public int Id { get; set; }  

        /// <summary>  
        /// 名称  
        /// </summary>  
        public string Name { get; set; }  
    }  
}  

9.4.2.3 EntityBase 示范:

using Furion.DatabaseAccessor;  

namespace Furion.Core  
{  
    public class User : EntityBase  // 或者 EntityBaseNotTenant,Furion 4.9.1.11+ 有效  
    {  
        // 无需定义 Id 属性  

        /// <summary>  
        /// 名称  
        /// </summary>  
        public string Name { get; set; }  
    }  
}  

9.4.2.4 Entity 示范:

using Furion.DatabaseAccessor;  

namespace Furion.Core  
{  
    public class User : Entity  // 或者 EntityNotTenant,Furion 4.9.1.11+ 有效  
    {  
        // 无需定义 Id 属性  
        // 并自动添加 CreatedTime,UpdatedTime 属性  

        /// <summary>  
        /// 名称  
        /// </summary>  
        public string Name { get; set; }  
    }  
}  

9.4.2.5 EntityNotKey 示范:

using Furion.DatabaseAccessor;  

namespace Furion.Core  
{  
    public class UserView : EntityNotKey  
    {  
        public UserView() : base("视图名称")  
        {  
        }  

        /// <summary>  
        /// Id  
        /// </summary>  
        public int Id { get; set; }  

        /// <summary>  
        /// 名称  
        /// </summary>  
        public string Name { get; set; }  
    }  
}  

特别注意在 Furion 框架中,数据库实体必须直接或间接继承 IEntity 才能进行仓储等操作。

9.4.3 自定义公共实体

在实际项目开发中,我们通常每个应用的数据库表都有一些公共的类,比如创建人,创建时间等,这个时候我们就需要自定义公共实体类了。

Furion 框架中,创建公共实体类需要满足以下条件:

  • 公共实体类必须是公开且是抽象类
  • 公共实体类必须含有无参构造函数
  • 公共实体类必须提供数据库定位器的支持

如:

using System;  
using System.ComponentModel.DataAnnotations;  
using System.ComponentModel.DataAnnotations.Schema;  

namespace Your.Namespace  
{  
    public abstract class CommonEntity : CommonEntity<int, MasterDbContextLocator>  
    {  
    }  

    public abstract class CommonEntity<TKey> : CommonEntity<TKey, MasterDbContextLocator>  
    {  
    }  

    public abstract class CommonEntity<TKey, TDbContextLocator1> : PrivateCommonEntity<TKey>  
        where TDbContextLocator1 : class, IDbContextLocator  
    {  
    }  

    public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2> : PrivateCommonEntity<TKey>  
        where TDbContextLocator1 : class, IDbContextLocator  
        where TDbContextLocator2 : class, IDbContextLocator  
    {  
    }  

    public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3> : PrivateCommonEntity<TKey>  
        where TDbContextLocator1 : class, IDbContextLocator  
        where TDbContextLocator2 : class, IDbContextLocator  
        where TDbContextLocator3 : class, IDbContextLocator  
    {  
    }  

    public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3, TDbContextLocator4> : PrivateCommonEntity<TKey>  
        where TDbContextLocator1 : class, IDbContextLocator  
        where TDbContextLocator2 : class, IDbContextLocator  
        where TDbContextLocator3 : class, IDbContextLocator  
        where TDbContextLocator4 : class, IDbContextLocator  
    {  
    }  

    public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3, TDbContextLocator4, TDbContextLocator5> : PrivateCommonEntity<TKey>  
        where TDbContextLocator1 : class, IDbContextLocator  
        where TDbContextLocator2 : class, IDbContextLocator  
        where TDbContextLocator3 : class, IDbContextLocator  
        where TDbContextLocator4 : class, IDbContextLocator  
        where TDbContextLocator5 : class, IDbContextLocator  
    {  
    }  

    public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3, TDbContextLocator4, TDbContextLocator5, TDbContextLocator6> : PrivateCommonEntity<TKey>  
        where TDbContextLocator1 : class, IDbContextLocator  
        where TDbContextLocator2 : class, IDbContextLocator  
        where TDbContextLocator3 : class, IDbContextLocator  
        where TDbContextLocator4 : class, IDbContextLocator  
        where TDbContextLocator5 : class, IDbContextLocator  
        where TDbContextLocator6 : class, IDbContextLocator  
    {  
    }  

    public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3, TDbContextLocator4, TDbContextLocator5, TDbContextLocator6, TDbContextLocator7> : PrivateCommonEntity<TKey>  
        where TDbContextLocator1 : class, IDbContextLocator  
        where TDbContextLocator2 : class, IDbContextLocator  
        where TDbContextLocator3 : class, IDbContextLocator  
        where TDbContextLocator4 : class, IDbContextLocator  
        where TDbContextLocator5 : class, IDbContextLocator  
        where TDbContextLocator6 : class, IDbContextLocator  
        where TDbContextLocator7 : class, IDbContextLocator  
    {  
    }  

    public abstract class CommonEntity<TKey, TDbContextLocator1, TDbContextLocator2, TDbContextLocator3, TDbContextLocator4, TDbContextLocator5, TDbContextLocator6, TDbContextLocator7, TDbContextLocator8> : PrivateCommonEntity<TKey>  
        where TDbContextLocator1 : class, IDbContextLocator  
        where TDbContextLocator2 : class, IDbContextLocator  
        where TDbContextLocator3 : class, IDbContextLocator  
        where TDbContextLocator4 : class, IDbContextLocator  
        where TDbContextLocator5 : class, IDbContextLocator  
        where TDbContextLocator6 : class, IDbContextLocator  
        where TDbContextLocator7 : class, IDbContextLocator  
        where TDbContextLocator8 : class, IDbContextLocator  
    {  
    }  

    public abstract class PrivateCommonEntity<TKey> : IPrivateEntity  
    {  
        // 注意是在这里定义你的公共实体  
        public virtual TKey Id { get; set; }  

        public virtual DateTime CreatedTime { get; set; }  

        // 更多属性定义  
    }  
}  

特别说明通过上面的格式定义可以完美的支持多数据库操作,建议采用这种格式,而且所有的公共属性都应该定义在 PrivateXXXX 有类中。

9.4.4 数据库实体配置

在过去的 EF Core 项目开发中,数据库实体配置需要在 DbContextOnModelCreating 中配置。Furion 为了简化配置和提高开发效率,抽象出了 IEntityTypeBuilder<TEntity> 接口。

通过 IEntityTypeBuilder<TEntity> 接口,我们无需在 DbContextOnModelCreating 中配置,可在任意地方配置。

9.4.4.1 在数据库实体中配置

using Furion.DatabaseAccessor;  
using Microsoft.EntityFrameworkCore;  
using Microsoft.EntityFrameworkCore.Metadata.Builders;  
using System;  

namespace Furion.Core  
{  
    public class User : Entity, IEntityTypeBuilder<User>  
    {  
        /// <summary>  
        /// 名称  
        /// </summary>  
        public string Name { get; set; }  

        /// <summary>  
        /// 年龄  
        /// </summary>  
        public int Age { get; set; }  

        // 配置数据库实体  
        public void Configure(EntityTypeBuilder<User> entityBuilder, DbContext dbContext, Type dbContextLocator)  
        {  
            entityBuilder.HasKey(u => u.Id);  
            entityBuilder.HasIndex(u => u.Name);  
        }  
    }  
}  

9.4.4.2 在任何实例类中配置

using Furion.DatabaseAccessor;  
using Microsoft.EntityFrameworkCore;  
using Microsoft.EntityFrameworkCore.Metadata.Builders;  
using System;  

namespace Furion.Core  
{  
    public class SomeClass : IEntityTypeBuilder<User>  
    {  
        public void Configure(EntityTypeBuilder<User> entityBuilder, DbContext dbContext, Type dbContextLocator)  
        {  
            entityBuilder.HasKey(u => u.Id);  
            entityBuilder.HasIndex(u => u.Name);  
        }  
    }  
}  

如上面例子,通过 SomeClass 配置 User 数据库实体。

特别注意SomeClass必须声明为public,否则无法自动注册。

更多知识如需了解实体配置支持哪些配置可查阅 【EFCore - 创建模型】 章节。

9.4.5 数据库实体配置说明

Furion 框架会自动扫描所有继承 IEntity 接口的类进行 DbSet<TEntity> 注册,也就是实现自动配置 DbContextOnModelCreating

如果需要跳过自动注册,只需要贴 [Manual][SuppressSniffer] 特性即可。一旦贴了此特性,那么就需要手动配置 DbContextOnModelCreating

9.4.6 配置列名及列类型

有时候我们需要手动设置列名或列类型,比如 decimal(18,2),这时候只需要在属性上面贴 [Column("列名", TypeName="decimal(18,2)")] 即可。

9.4.7 配置数据库表名和 Schema

可以通过在实体类型贴 [Table("表名", Schema = "dbo")] 配置。

9.4.8 反馈与建议

与我们交流给 Furion 提 Issue